package com.bootdo.check.service.impl;

import java.io.File;
import java.util.Arrays;
import java.util.concurrent.Callable;

import javax.swing.JFrame;

import org.bytedeco.javacpp.opencv_features2d;
import org.bytedeco.javacpp.opencv_core.DMatch;
import org.bytedeco.javacpp.opencv_core.DMatchVector;
import org.bytedeco.javacpp.opencv_core.DMatchVectorVector;
import org.bytedeco.javacpp.opencv_core.KeyPointVector;
import org.bytedeco.javacpp.opencv_core.Mat;
import org.bytedeco.javacpp.opencv_features2d.DescriptorMatcher;
import org.bytedeco.javacpp.opencv_xfeatures2d.SURF;
import org.bytedeco.javacv.CanvasFrame;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.OpenCVFrameConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.bootdo.check.dao.VideoDao;
import com.bootdo.check.domain.DetectResult;
import com.bootdo.check.domain.VideoDO;
import com.bootdo.check.service.ColorLogo;
import com.bootdo.common.utils.SpringContextHolder;

/**
 * 
 * @author ke
 *
 */
public class Matcher implements Callable<DetectResult>, AutoCloseable {

	private static final Logger logger = LoggerFactory.getLogger(Matcher.class);

	// SURF初始化参数
	private static final double HESSION_THRESHOLD = 50d;
	private static final int N_OCTAVES = 4;
	private static final int N_OCTAVE_LAYERS = 3;
	private static final boolean EXTENDED = false;
	private static final boolean UPRIGHT = true;
	private static final float NNRADIO = 0.7f;

	private FFmpegFrameGrabber grabber;

	// 视频分辨率
	private int height;
	private int width;

	// 视频头之后与尾之前的时间戳
	private long afterHead;
	private long beforeTail;

	private DetectResult detectResult = new DetectResult();
	
	private final ColorLogo colorLogo;

	public Matcher(File video, int id, ColorLogo colorLogo) throws org.bytedeco.javacv.FrameGrabber.Exception {
		this.colorLogo = colorLogo;
		grabber = FFmpegFrameGrabber.createDefault(video);
		grabber.start();
		height = grabber.getImageHeight();
		width = grabber.getImageWidth();
		afterHead = 1000000;
		beforeTail = grabber.getLengthInTime() - 1000000;
		detectResult.setId(id);
		detectResult.setFile(video);

		if (logger.isDebugEnabled()) {
			logger.debug(grabber.getMetadata().toString());
		}
	}

	 private Player player = new Player();

	@Override
	public DetectResult call() throws Exception {

		try {
			detectResult.setHasHead(hasHead());
			detectResult.setHasTail(hasTail());

			logger.debug("afterHead=>{}, beforeTail=>{}", afterHead, beforeTail);

			// 时间单位为微秒
			int count = 0;
			int matched = 0;
			int[] countLocation = new int[4];
			for (long i = afterHead; i < beforeTail; i += 1000000) {
				try {
					grabber.setTimestamp(i);
					Frame frame = grabber.grabKeyFrame();
					if (frame != null && frame.image != null) {
						count++;
						if (match(frame, countLocation, false)) {
							matched++;
						}
					}
				} catch (Exception e) {
					logger.error("图像匹配失败=>" + e.getMessage(), e);
				}

			}

			logger.info("matched=>{}, total=>{}, countLocation=>{}", matched, count, Arrays.toString(countLocation));

			if (count == 0 || matched * 1.0 / count < 0.4) {
				return detectResult;
			}

			int max = 0;
			for (int i = 1; i < countLocation.length; i++) {
				if (countLocation[i] > countLocation[max]) {
					max = i;
				}
			}
			// 当匹配中有一半以上在某个角时才确定logo位置
			if (1.0 * countLocation[max] / matched > 0.5) {
				detectResult.setLogoLoaction(max + 1);
			}

			grabber.close();
			logger.debug("detectResult=>{}", detectResult);
		} catch (Exception e) {
			logger.error("123异常=>" + e.getMessage(), e);
			VideoDao videoDao = SpringContextHolder.getBean("videoDao");
			VideoDO videoDO = new VideoDO();
			videoDO.setChekcResult("检测异常");
			videoDO.setStatus("检测异常");
			videoDO.setIdvideo(detectResult.getId());
			videoDao.update(videoDO);
			return null;
		}
		return detectResult;
	}

	private boolean match(Frame frame, int[] countLocation, boolean isHead) {

		try (SURF surf = SURF.create(HESSION_THRESHOLD, N_OCTAVES, N_OCTAVE_LAYERS, EXTENDED, UPRIGHT);
				DescriptorMatcher descriptorMatcher = DescriptorMatcher.create(DescriptorMatcher.FLANNBASED);
				KeyPointVector imgPoints = new KeyPointVector();
				Mat imgDescriptors = new Mat();) {
			OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();

			Mat img = converter.convertToMat(frame);

			surf.detect(img, imgPoints);
			surf.compute(img, imgPoints, imgDescriptors);

			if (imgDescriptors.isNull() || imgDescriptors.empty() || imgDescriptors.arraySize() == 0
					|| imgPoints.size() < 3) {
				return false;
			}
			DMatchVectorVector matches = new DMatchVectorVector();
			descriptorMatcher.knnMatch(colorLogo.getDescriptors(), imgDescriptors, matches, 2);

			DMatchVector goodMatches = selectBestMatches(matches);

			 show(imgPoints, img, goodMatches);

			logger.debug("匹配特征点数量=>{}", goodMatches.size());

			if (goodMatches.size() > 7) {
				double dx = 0, dy = 0;
				for (long i = 0; i < goodMatches.size(); i++) {
					DMatch dMatch = goodMatches.get(i);
					dx += imgPoints.get(dMatch.trainIdx()).pt().x();
					dy += imgPoints.get(dMatch.trainIdx()).pt().y();
				}
				if (logger.isDebugEnabled()) {
					logger.debug("平均坐标 x=>{}, y=>{}", dx / goodMatches.size(), dy / goodMatches.size());
				}
				if (!isHead) {
					if (dx / goodMatches.size() / width < 0.5) {
						if (dy / goodMatches.size() / height < 0.5) {
							countLocation[0]++;
						} else {
							countLocation[1]++;
						}
					} else {
						if (dy / goodMatches.size() / height < 0.5) {
							countLocation[3]++;
						} else {
							countLocation[2]++;
						}
					}
				} else {
					double cx = dx / goodMatches.size() / width;
					double cy = dy / goodMatches.size() / height;
					return (0.33 < cx && cx < 0.66 && 0.33 < cy && cy < 0.66);
				}
				return true;
			} else {
				return false;
			}

		}
	}

	private void show(KeyPointVector imgPoints, Mat img, DMatchVector goodMatches) {
		Mat outImg = new Mat();
		opencv_features2d.drawMatches(colorLogo.getLogo(), colorLogo.getPoints(), img, imgPoints, goodMatches, outImg);

		if (!outImg.empty()) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			 player.show(outImg);
		}
	}

	private boolean hasHead() {
		int count = 0;

		for (long i = afterHead; i < 30000000; i += 1000000) {
			try {
				grabber.setTimestamp(i);
				Frame frame = grabber.grabKeyFrame();
				if (frame != null && frame.image != null) {
					if (match(frame, null, true)) {
						count++;
					} else {
						count = 0;
					}
				}
				// 当连续三秒logo检测在中心时，判断有视频头
				if (count == 3) {
					afterHead = i;
					return true;
				}
			} catch (org.bytedeco.javacv.FrameGrabber.Exception e) {
				logger.error("头视频帧获取失败=>" + e.getMessage(), e);
			}
		}
		return false;
	}

	private boolean hasTail() {
		int count = 0;

		for (long i = beforeTail - 30000000; i < beforeTail; i += 1000000) {
			try {
				grabber.setTimestamp(i);
				Frame frame = grabber.grabKeyFrame();
				if (frame != null && frame.image != null) {
					if (match(frame, null, true)) {
						count++;
					} else {
						count = 0;
					}
				}
				// 当连续三秒logo检测在中心时，判断有视频尾
				if (count == 3) {
					beforeTail = i - 3000000;
					return true;
				}
			} catch (org.bytedeco.javacv.FrameGrabber.Exception e) {
				logger.error("尾视频帧获取失败" + e.getMessage(), e);
			}
		}
		return false;
	}

	private DMatchVector selectBestMatches(DMatchVectorVector matches) {
		DMatchVector goodMatches = new DMatchVector();
		for (int i = 0; i < matches.size(); i++) {
			DMatch m1 = matches.get(i).get(0);
			DMatch m2 = matches.get(i).get(1);

			if (m1.distance() <= m2.distance() * NNRADIO) {
				goodMatches.push_back(m1);
			}
		}
		return goodMatches;
	}

	@Override
	public void close() throws Exception {
		grabber.close();
	}

	class Player {

		public Player() {
			canvas = new CanvasFrame("video", 1);
			canvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		}

		private CanvasFrame canvas;

		void show(Mat image) {
			OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();
			canvas.showImage(converter.convert(image));
		}
	}
}
